package com.semlab.server.enrichment;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.semlab.server.domain.Actor;
import com.semlab.server.domain.Resource;
import org.neo4j.cypher.javacompat.ExecutionResult;
import org.neo4j.graphdb.Relationship;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.semlab.server.domain.Client;
import com.semlab.server.domain.Movie;
import com.semlab.server.graph.ConceptRepository;
import com.semlab.server.graph.Neo4jTemplate;
import com.semlab.server.resources.Concept;
import com.semlab.server.resources.SLog;
import com.semlab.shared.NamespaceConstants;

@Component
public class DataMiner {

    @Autowired
    Neo4jTemplate template;

    @Autowired
    ConceptRepository concepts;

    // public List<Movie> getAllMovies(String userId) {
    // List<Movie> list = getMyMovies(userId);
    // list.addAll(getFriendsMovies(userId));
    // return list;
    // }

    public List<Movie> getMyMovies(String userId) {
        return parseMovies(template.executeQuery(String.format(
                "start n=node(0) match (n)-[:ROOT]->(p)-[:LIKES]->(movie) where p.concept_id='%s' return movie.concept_id, movie.name, movie.fb_hits", userId)));
    }

    public List<Movie> getFriendsMovies(String userId, int offset, int total) {
        // template.executeQuery("start n=node(0) match (n)-[:ROOT]->(p)-[:FRIEND]->(f)-[:LIKES]->(l) return l.name, l.fb_hits ORDER BY l.fb_hits DESC ",
        // true);

        return parseMovies(template
                .executeQuery(String
                        .format("start n=node(0) match (n)-[:ROOT]->(p)-[:FRIEND]->(f)-[:LIKES]->(movie) where p.concept_id ='%s' return distinct movie.concept_id, movie.name, movie.fb_hits, movie.uri?, movie.imdb_uri?, movie.imdb_rating?, movie.enriched? ORDER BY movie.fb_hits DESC SKIP "
                                + offset + " LIMIT " + total, userId)));
    }

    public List<Client> getFriendsForMovie(Movie movie) {
        return parseFriends(template
                .executeQuery(String
                        .format("start n=node(0) match (n)-[:ROOT]->(p)-[:FRIEND]->(f)-[:LIKES]->(movie) where movie.concept_id ='%s' return f.concept_id, f.first_name,f.last_name ",
                                movie.getConceptId())));
    }

    private List<Client> parseFriends(ExecutionResult result) {
        ArrayList<Client> friends = new ArrayList<Client>();
        for (Map<String, Object> row : result) {
            Client friend = new Client();
            for (Entry<String, Object> column : row.entrySet()) {
                if (column.getKey().equals("f.concept_id")) {
                    friend.setFacebookId(column.getValue().toString());
                } else if (column.getKey().equals("f.first_name")) {
                    friend.setFirstName(column.getValue().toString());
                } else if (column.getKey().equals("f.last_name")) {
                    friend.setLastName(column.getValue().toString());
                }
            }
            friends.add(friend);
        }
        return friends;
    }

    public void listSemAwareBooks() {
        template.executeQuery("start n=node(0) match (n)-[:ROOT]->(p)-[:LIKES]->(movie) where has(movie.uri) return movie.name, movie.uri", true);
        template.executeQuery("start n=node(0) match (n)-[:ROOT]->(p)-[:FRIEND]->(f)-[:LIKES]->(movie) where has(movie.uri) return movie.name, movie.uri", true);
    }

    public void statistics() {
        int myLost = parseCount(template.executeQuery("start n=node(0) match (n)-[:ROOT]->(p)-[:LIKES]->(movie) where not has(movie.uri) return count(*)"));
        int otherLost = parseCount(template
                .executeQuery("start n=node(0) match (n)-[:ROOT]->(p)-[:FRIEND]->(f)-[:LIKES]->(movie) where not has(movie.uri) return count(*)"));
        int myFound = parseCount(template.executeQuery("start n=node(0) match (n)-[:ROOT]->(p)-[:LIKES]->(movie) where has(movie.uri) return count(*)"));
        int otherFound = parseCount(template
                .executeQuery("start n=node(0) match (n)-[:ROOT]->(p)-[:FRIEND]->(f)-[:LIKES]->(movie) where has(movie.uri) return count(*)"));

        int totalFound = myFound + otherFound;
        int total = (myFound + myLost + otherFound + otherLost);
        double coverage = (double) totalFound * 100 / total;
        DecimalFormat twoDForm = new DecimalFormat("#.##");

        SLog.debug(getClass(), "found = " + totalFound);
        SLog.debug(getClass(), "lost = " + (myLost + otherLost));
        SLog.debug(getClass(), "total=" + total);
        SLog.debug(getClass(), "percent of coverage", twoDForm.format(coverage) + "%");
    }

    private List<Movie> parseMovies(ExecutionResult result) {
        ArrayList<Movie> movies = new ArrayList<Movie>();
        for (Map<String, Object> row : result) {
            Movie movie = new Movie();
            for (Entry<String, Object> column : row.entrySet()) {
                if (column.getKey().equals("movie.name")) {
                    movie.setTitle(column.getValue().toString());
                } else if (column.getKey().equals("movie.concept_id")) {
                    movie.setConceptId(column.getValue().toString());
                } else if (column.getKey().equals("movie.fb_hits")) {
                    movie.setHits(Integer.parseInt(column.getValue().toString()));
                } else if (column.getKey().equals("movie.uri?") && column.getValue() != null) {
                    movie.setUri(column.getValue().toString());
                } else if (column.getKey().equals("movie.imdb_uri?") && column.getValue() != null) {
                    movie.setImdbUri(column.getValue().toString());
                } else if (column.getKey().equals("movie.imdb_rating?") && column.getValue() != null) {
                    movie.setRating(Double.parseDouble(column.getValue().toString()));
                } else if (column.getKey().equals("movie.enriched?") && column.getValue() != null) {
                    movie.setEnriched(Boolean.parseBoolean(column.getValue().toString()));
                }
            }
            movies.add(movie);
        }
        return movies;
    }

          //TODO make this general
    public ArrayList<Actor> parseActors(Concept concept) {
        ArrayList<Actor> actors = new ArrayList<Actor>();
        ExecutionResult result = template
                .executeQuery(String
                        .format("start n=node:concepts(%s = \"%s\") match (n)-[:`%s`]->(a) return a.uri, a.`%s`?", NamespaceConstants.NAME + NamespaceConstants.TYPE,
                                concept.getId(), NamespaceConstants.DBPEDIA_STARRING, NamespaceConstants.RDFS_LABEL), false);
        for (Map<String, Object> row : result) {
            Actor actor = new Actor();
            for (Entry<String, Object> column : row.entrySet()) {
                if (column.getKey().equals("a.uri")) {
                    actor.setUri(column.getValue().toString());
                } else if (column.getKey().equals("a." + NamespaceConstants.RDFS_LABEL + "?") && column.getValue() != null) {
                    actor.setName(column.getValue().toString());
                }
            }
            if(actor.getName() == null){
                actor.setName(actor.getUri());
            }
            actors.add(actor);
        }
        return actors.size() > 0 ? actors : null;
    }

    public ArrayList<Resource> parseResources(Concept concept) {
        ArrayList<Resource> resources = new ArrayList<Resource>();
        ExecutionResult result = template
                .executeQuery(String
                        .format("start n=node:concepts(%s = \"%s\") match (n)-[link]->(a) return link, a.uri, a.`%s`?", NamespaceConstants.NAME + NamespaceConstants.TYPE,
                                concept.getId(), NamespaceConstants.RDFS_LABEL), false);
        for (Map<String, Object> row : result) {
            Resource resource = new Resource();
            for (Entry<String, Object> column : row.entrySet()) {
                if (column.getKey().equals("a.uri")) {
                    resource.setUri(column.getValue().toString());
                } else if (column.getKey().equals("a." + NamespaceConstants.RDFS_LABEL + "?")) {
                    if (column.getValue() != null) {
                        resource.setName(column.getValue().toString());
                    }
                }else{
//                    System.out.println(column.getKey());
                    resource.setLink(((Relationship) column.getValue()).getType().toString());
                }
            }
            if(resource.getName() == null){
                resource.setName(resource.getUri());
            }
            resources.add(resource);
        }
        return resources.size() > 0 ? resources : null;
    }

    private int parseCount(ExecutionResult result) {
        for (Map<String, Object> row : result) {
            for (Entry<String, Object> column : row.entrySet()) {
                if (column.getKey().equals("count(*)")) {
                    return Integer.parseInt(column.getValue().toString());
                }
            }
        }
        return -1;
    }

    public Movie getDetailsForAMovie(Movie movie) {
        Concept concept = concepts.getById(movie.getConceptId());
        assert concept != null;
        return parseMovieInDetail(concept);
    }


    public Movie parseMovieInDetail(Concept concept) {
        Movie movie = new Movie();
        movie.setConceptId(concept.getId());
        movie.setTitle(concept.getName());
        movie.setUri(concept.getUri());
        movie.setEnriched(concept.isEnriched());
        movie.setImdbUri(getProperty(NamespaceConstants.IMDB_URI, concept));
        movie.setWikiAbstract(getProperty(NamespaceConstants.DBPEDIA_ABSTRACT, concept));
        movie.setGenre(getProperty(NamespaceConstants.IMDB_GENRE, concept));
        movie.setPlot(getProperty(NamespaceConstants.IMDB_PLOT, concept));
        movie.setImageUrl(getProperty(NamespaceConstants.IMDB_POSTER_64, concept));
        movie.setBudget(getProperty(NamespaceConstants.DBPEDIA_BUDGET, concept));

//        String runtime = getProperty(NamespaceConstants.DBPEDIA_RUNTIME, concept);
//        if (runtime != null)
//            movie.setRuntime(Double.parseDouble(runtime) / 60);
        movie.setRuntime(getProperty(NamespaceConstants.DBPEDIA_RUNTIME, concept));
//        movie.setActors(parseActors(concept));
        movie.setResources(parseResources(concept));
        return movie;
    }


    public String getProperty(String property, Concept concept) {
        Object obj = concept.getProperty(property);
//        try {
//            String[] ob = (String[]) obj;
//            obj = ob[0];
//        } catch (Exception e) {
//        }
        return (obj != null) ? obj.toString() : null;
    }

    public int countFriendsMovies(String facebookId) {
        return parseCount(template
                .executeQuery(String.format("start n=node(0) match (n)-[:ROOT]->(p)-[:FRIEND]->(f)-[:LIKES]->(movie) where p.concept_id='%s' return count(*)", facebookId)));
    }

}
